/*    
    OpenMheg - A free MHEG-5 library
    Copyright (C) 2008 Dharmesh Malam

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Core.Model.Data;
using Core.Model.Data.Variables;
using Core.Model.Data.Generics;
using System.Globalization;
using Core.Model.Data.Type;
using Engine;

namespace Core.Model.Ingredients
{
    public class ResidentProgram : Program
    {

        private DateTime Julian = new DateTime( 1858, 11, 17, 0, 0, 0 );

        #region   Actions
        public override void Call ( BooleanVariable callSucceeded, List<Generic> parameters )
        {
            base.Call( callSucceeded, parameters );
        }

        public override void InittInternalAttributes ()
        {
            base.InittInternalAttributes();

            MethodInfo [] residentPrograms = this.GetType().GetMethods();

            foreach ( MethodInfo methodInfo in residentPrograms )
            {
                if ( methodInfo.Name == Name.ToString() )
                {
                    namedProgram = methodInfo;
                    return; 
                }
            }

            if ( NamedProgram == null )
                log.Error( "InitInternalAttributes: Can't find resident program "+Name+"." );
        }
        #endregion

        #region  Implementation specific

        private MethodInfo namedProgram = null;

        protected MethodInfo NamedProgram
        {
            get { return namedProgram; }
            set { namedProgram = value; }
        }

        public override bool Execute ( Generic [] parameters )
        {

            if ( NamedProgram == null )
            {
                log.Error( "Execute:Unknown resident program called. Ignoring..." );
                return false;
            }

            
            ParameterInfo [] namedProgramParams = NamedProgram.GetParameters();

            if ( parameters.Length >= namedProgramParams.Length )
            {
                for ( int i = 0 ; i < namedProgramParams.Length ; i++ )
                {
                    if ( !namedProgramParams [i].ParameterType.IsInstanceOfType( parameters [i] ))
                    {
                        log.Error( "Execute: Parameter "+i+"("+parameters [i].GetType()+") is not "+namedProgramParams [i].GetType()+"." );
                        return false;
                    }
                }

                try
                {
                    Object [] actualParames = new object [namedProgramParams.Length];

                    Array.Copy( parameters, actualParames, namedProgramParams.Length );

                    NamedProgram.Invoke( this, actualParames );
                    return true;
                }
                catch ( TargetInvocationException e )
                {
                    log.Error( "Execute: Can not invoke Resisdent Program("+NamedProgram+"("+namedProgramParams+") ).", e.InnerException );
                    throw  e.InnerException ;
                    return false;
                }

            }
            else
            {
                log.Error( "Execute: Resident Program ("+NamedProgram.Name+") requires at least "+namedProgramParams.Length+" parameters"
                                +"but only given "+parameters.Length+"." );
                return false;
            }

        }
        #endregion

        #region  Implementation of ResidentPrograms

        //  3.10.2 - Date and time functions
        
        // 3.10.2.1 - GetCurrentDate
        public void GCD ( GenericInteger date, GenericInteger time )
        {
                        
            TimeSpan gcd = DateTime.Now.Subtract( Julian );

            date.Value = gcd.Days;
            time.Value = ( gcd.Hours * 60 * 60 ) + ( gcd.Minutes * 60 ) + gcd.Seconds + (int) Math.Round(( gcd.Milliseconds / 1000.00 ) );
            
        }

        // 3.10.2.3 - FormatDate
        public void FDa ( GenericOctetString dateFormat, GenericInteger date, GenericInteger time, GenericOctetString dateString )
        {
            String format = dateFormat.ToString();
            
            format = format.Replace( "%Y", "yyyy" );    //  Years, 4 digits
            format = format.Replace( "%y", "yy" );      //  Years, 2 digits
            format = format.Replace( "%X", "MM" );      //  Months, with padding 0
            format = format.Replace( "%x", "M" );       //  Months, without padding 0
            format = format.Replace( "%D", "dd" );      //  Days, with padding 0
            format = format.Replace( "%d", "%d" );       //  Days, without padding 0
            format = format.Replace( "%H", "HH" );      //  Hours, with padding 0, 24hr clock
            format = format.Replace( "%h", "H" );       //  Hours, without padding 0, 24hr clock
            format = format.Replace( "%I", "hh" );      //  Hours, with padding 0, 12hr clock
            format = format.Replace( "%i", "h" );       //  Hours, without padding 0, 12hr clock
            format = format.Replace( "%M", "mm" );      //  Minutes, with padding 0
            format = format.Replace( "%m", "%m" );       //  Minutes, without padding 0
            format = format.Replace( "%S", "ss" );      //  Seconds, with padding 0
            format = format.Replace( "%s", "%s" );       //  Seconds, without padding 0
            format = format.Replace( "%A", "tt" );       //  AM/PM marker
            format = format.Replace( "%a", "%t" );       //  am/pm => a/p
            format = format.Replace( "%%", "%" );       //  %% => %

            DateTime calculated = GetDate( date.Value, time.Value );
            //dateString.Value = calculated.ToString(format,DateTimeFormatInfo.InvariantInfo );
            dateString.Value = new OctetString(calculated.ToString( format));

        }

        // 3.10.2.4 - GetDayOfWeek
        public void GDW ( GenericInteger date, GenericInteger dayOfWeek )
        {
            //  Retrieve a calendar instance, set its time and retrieve the current day of the week
            DateTime calculated = GetDate( date.Value );
            //0 for sunday , 1 for monday ... 6 for saturday
            dayOfWeek.Value = (int) calculated.DayOfWeek;

        }

        // 3.10.3 - Random number functions
        // 3.10.3.1 - Random
        public void Rnd ( GenericInteger num, GenericInteger random )
        {
            //  Generate a random number from 1..num
            Random ran  = new Random();
            random.Value = ran.Next(1,num.Value);
        }

        //  3.10.4 - Type conversion functions

        // 3.10.4.1 - CastToContentRef
        public void CTC ( GenericOctetString str, GenericContentRef contentRef )
        {
            contentRef.Value = new ContentRef(str.Value);
        }

        // 3.10.4.2 - CastToObjectRef
        public void CTO ( GenericOctetString str, GenericInteger objectId, GenericObjectRef objectRef )
        {
            objectRef.Value = new ObjectRef(str.Value, objectId.Value);
        }

        
        //  3.10.5 - String manipulation functions
                                
        //CompareString
        public void CSt ( GenericOctetString str1, GenericOctetString str2, GenericInteger compareResult )
        {
            compareResult.Value = str1.Value.CompareTo( str2.Value );
            
        }

        // 3.10.5.1 - GetStringLength
        public void GSL ( GenericOctetString str, GenericInteger length )
        {
            length.Value = ( str.Value.Length );
        }

        // 3.10.5.2 - GetSubString
        public void GSS ( GenericOctetString str, GenericInteger beginExtract, GenericInteger endExtract, GenericOctetString stringResult )
        {
            
            int begin = beginExtract.Value < 1 ? 1 : ( beginExtract.Value > str.Value.Length ?  str.Value.Length : beginExtract.Value );
            int end = endExtract.Value < 1 ? 1 :  ( endExtract.Value > str.Value.Length ? str.Value.Length : endExtract.Value );
            
            //if ( begin < 1 )
            //    begin = 1;
            //if ( begin > str.Value.Length )
            //    begin = str.Value.Length;
            //if ( end < 1 )
            //    end = 1;
            //if ( end > str.Value.Length )
            //    end = str.Value.Length;

            if ( end < begin || (end == 0 && begin == 0))
                stringResult.Value = OctetString.Empty;
            else
                stringResult.Value = str.Value.Substring( begin -1, ( end - begin ) +1 );
        }

        // 3.10.5.3 - SearchSubString
        public void SSS ( GenericOctetString str, GenericInteger startIndex, GenericOctetString searchString, GenericInteger stringPosition )
        {

            int start = startIndex.Value < 1 ? 1 : ( startIndex.Value > str.Value.Length ?  str.Value.Length : startIndex.Value );
            
            
            //if ( startIndex.Value < 1 )
            //    startIndex.Value = 1;
            //if (str.Value!= null && startIndex.Value > str.Value.Length )
            //    startIndex.Value = str.Value.Length;

            //if (str.Value == null || str.Value == "")
            if (str.Value == OctetString.Empty)
              stringPosition.Value = (-1);
          else if ( searchString.Value == OctetString.Empty )
              stringPosition.Value = start;
          else
          {
              int position = ( str.Value.IndexOf( searchString.Value, start - 1 ) );
              if ( position < 0 )
                  stringPosition.Value = - 1;
              else
                  stringPosition.Value = position + 1;

          }
        }

        // 3.10.5.4 - SearchAndExtractSubstring
        public void SES ( GenericOctetString str, GenericInteger startIndex, GenericOctetString searchString, GenericOctetString stringResult, GenericInteger stringPosition )
        {

            int start = startIndex.Value < 1 ? 1 : ( startIndex.Value > str.Value.Length ?  str.Value.Length : startIndex.Value );

            //if ( startIndex.Value < 1 )
            //    startIndex.Value = 1;
            //if ( startIndex.Value > str.Value.Length )
            //    startIndex.Value = str.Value.Length;


            if ( str.Value == OctetString.Empty )
            {
                stringResult.Value = OctetString.Empty;
                stringPosition.Value = ( -1 );

            }
            else if (searchString.Value == OctetString.Empty)
            {
                stringResult.Value = OctetString.Empty;
                stringPosition.Value =  start;
            }
            else
            {
                int position = str.Value.IndexOf( searchString.Value, start - 1 );

                if ( position != -1 )
                {
                    stringResult.Value = ( str.Value.Substring( start - 1, position - (start - 1) ) );
                    stringPosition.Value =  position + 1 + searchString.Value.Length;
                }
                else
                {
                    stringResult.Value = OctetString.Empty;
                    stringPosition.Value = ( -1 );
                }
            }
        }

        // 3.10.6 Service selection

        //  3.10.6 - SI_GetServiceIndex - Service selection functions
        public void GSI ( GenericOctetString serviceReference, GenericInteger serviceIndex )
        {
            serviceIndex.Value = Engine.MHEGEngine.Instance.Tuner.GetServiceIndex( serviceReference.Value );
            
        }

        // 3.10.6.2 - SI_TuneIndex - never fork
        public void TIn ( GenericInteger serviceIndex )
        {
            Engine.MHEGEngine.Instance.Tuner.TuneServiceIndex( serviceIndex.Value );
        }

        //3.10.6.3 SI_GetBasicSI
        public void BSI ( GenericInteger serviceIndex, GenericInteger networkId, GenericInteger origNetworkId, GenericInteger transportStreamId, GenericInteger serviceId )
        {
            
            int[] ids = Engine.MHEGEngine.Instance.Tuner.GetBasicSI( serviceIndex.Value );

            if ( ids.Length == 4 )
            {
                networkId.Value = ids [0];
                origNetworkId.Value = ids [1];
                transportStreamId.Value = ids [2];
                serviceId.Value = ids [3];
            }
            else throw new Exception( "Wrong number of ids" );

        }

        // 3.10.6.4 - SI_TuneIndexInfo
        public void TII ( GenericInteger tuneInfo )
        {
            Engine.MHEGEngine.Instance.Tuner.Tuning = (Engine.MHEGTuner.TuneInfo) tuneInfo.Value;
        }

        // 3.10.7 - Reference checking functions

        //3.10.7.1 - CheckContentRef
        public void CCR ( GenericContentRef refToCheck, GenericBoolean refValidVar, GenericContentRef refCheckedVar )
        {
            throw new Exception( "Fix me" );
            refValidVar.Value = Engine.MHEGEngine.Instance.CheckContent( refToCheck.Value );
            refCheckedVar.Value = refToCheck.Value;
        }

        //3.10.7.2 - CheckGroupIDRef
        public void CGR ( GenericObjectRef refToCheck, GenericBoolean refValidVar, GenericObjectRef refCheckedVar )
        {
            //TODO Implement CGR
            throw new Exception( "Fix me" );
        //    refValidVar.Value = Engine.MHEGEngine.Instance.CheckGroup( refToCheck.Value );
        //    refCheckedVar.Value = refToCheck.Value;
        }

        //3.10.7.3 - GetBootInfo
        public void GBI ( GenericBoolean infoResult, GenericOctetString bootInfo )
        {
            //TODO Implement GBI
            throw new Exception( "Fix me" );
         //   infoResult.Value = false;
         //   bootInfo.Value = "";
        }

        //3.10.8 Presentation Information

        //3.10.8.1 VideoToGraphics
        public void VTG ( GenericInteger videoX, GenericInteger videoY, GenericInteger graphicsX, GenericInteger graphicsY )
        {
            //TODO Implement VTG
            throw new Exception( "Fix me" );
        }

        //3.10.8.2 - SetWideScreenAlignment
        public void SWA( GenericInteger mode)
        {
            Engine.MHEGEngine.Instance.Tuner.WidescreenAlignment = ( Engine.MHEGTuner.WidescreenAlignmentEnum ) mode.Value;
        }

        //3.10.8.3 - GetDisplayAspectRatio
        public void GDA ( GenericInteger aspectRatio )
        {
            aspectRatio.Value = ( int ) Engine.MHEGEngine.Instance.Tuner.AspectRatio;
        }

        //3.10.8.4 - SetSubtitleMode
        public void SSM ( GenericBoolean on )
        {
            Engine.MHEGEngine.Instance.Tuner.SubtitlesOn = on.Value;
        }

        //3.10.9 - Conditional access

        //3.10.9.1 - CI_SendMessage - not fork
        public void CIS ( GenericOctetString Message, GenericOctetString Response )
        {
            throw new Exception( "Fix me" );
        }

        //3.10.10 Developer utilities

        //3.10.10.1 - WhoAmI
        public void WAI ( GenericOctetString ident )
        {
            ident.Value = new OctetString(MHEGConstants.Identifier);
        }

        //3.10.10.2 - Debug
        public void Debug ()
        {
            throw new Exception( "Fix me" );
        }
        #endregion

        #region  Implementation specific
        public DateTime GetDate ( long date )
        {
            return GetDate( date, 0 );
        }

        public DateTime GetDate ( long date, long time )
        {

            try
            {
                long msPerDay = 24 * 60 * 60 * 1000;
                //ticks = number of 100 nano second periods since midnight 1/1/0001
                long j = Julian.Ticks + (10000 * msPerDay * date) + (time * 10000000);

                return new DateTime( j );

            }
            catch ( Exception e )
            {
                
                log.Error( "Could not parse date correctly; returning Julian" );
                return Julian;
            }

        }

        #endregion
    }
}
